(function() {
    'use strict';

    /**
     * @ngdoc overview
     * @name ui.grid.pagination
     *
     * @description
     *
     * #ui.grid.pagination
     * This module provides pagination support to ui-grid
     */
    var module = angular.module('ui.grid.pagination', ['ng', 'ui.grid']);

    /**
     * @ngdoc service
     * @name ui.grid.pagination.service:uiGridPaginationService
     *
     * @description Service for the pagination feature
     */
    module.service('uiGridPaginationService', ['gridUtil',
        function (gridUtil) {
            var service = {
                /**
                 * @ngdoc method
                 * @name initializeGrid
                 * @methodOf ui.grid.pagination.service:uiGridPaginationService
                 * @description Attaches the service to a certain grid
                 * @param {Grid} grid The grid we want to work with
                 */
                initializeGrid: function (grid) {
                    service.defaultGridOptions(grid.options);

                    /**
                     * @ngdoc object
                     * @name ui.grid.pagination.api:PublicAPI
                     *
                     * @description Public API for the pagination feature
                     */
                    var publicApi = {
                        events: {
                            pagination: {
                                /**
                                 * @ngdoc event
                                 * @name paginationChanged
                                 * @eventOf ui.grid.pagination.api:PublicAPI
                                 * @description This event fires when the pageSize or currentPage changes
                                 * @param {int} currentPage requested page number
                                 * @param {int} pageSize requested page size
                                 */
                                paginationChanged: function (currentPage, pageSize) { }
                            }
                        },
                        methods: {
                            pagination: {
                                /**
                                 * @ngdoc method
                                 * @name getPage
                                 * @methodOf ui.grid.pagination.api:PublicAPI
                                 * @description Returns the number of the current page
                                 */
                                getPage: function () {
                                    return grid.options.enablePagination ? grid.options.paginationCurrentPage : null;
                                },
                                /**
                                 * @ngdoc method
                                 * @name getTotalPages
                                 * @methodOf ui.grid.pagination.api:PublicAPI
                                 * @description Returns the total number of pages
                                 */
                                getTotalPages: function () {
                                    if (!grid.options.enablePagination) {
                                        return null;
                                    }
                                    grid.options.pages = publicApi.methods.pagination.generatePagesArray(grid.options.paginationCurrentPage,grid.options.totalItems,grid.options.paginationPageSize);
                                    return (grid.options.totalItems === 0) ? 1 : Math.ceil(grid.options.totalItems / grid.options.paginationPageSize);
                                },
                                /**
                                 * @ngdoc method
                                 * @name nextPage
                                 * @methodOf ui.grid.pagination.api:PublicAPI
                                 * @description Moves to the next page, if possible
                                 */
                                nextPage: function () {
                                    if (!grid.options.enablePagination) {
                                        return;
                                    }

                                    if (grid.options.totalItems > 0) {
                                        grid.options.paginationCurrentPage = Math.min(
                                            grid.options.paginationCurrentPage + 1,
                                            publicApi.methods.pagination.getTotalPages()
                                        );
                                    } else {
                                        grid.options.paginationCurrentPage++;
                                    }
                                    grid.options.pages = publicApi.methods.pagination.generatePagesArray(grid.options.paginationCurrentPage,grid.options.totalItems,grid.options.paginationPageSize);
                                },
                                /**
                                 * @ngdoc method
                                 * @name previousPage
                                 * @methodOf ui.grid.pagination.api:PublicAPI
                                 * @description Moves to the previous page, if we're not on the first page
                                 */
                                previousPage: function () {
                                    if (!grid.options.enablePagination) {
                                        return;
                                    }

                                    grid.options.paginationCurrentPage = Math.max(grid.options.paginationCurrentPage - 1, 1);
                                    grid.options.pages = publicApi.methods.pagination.generatePagesArray(grid.options.paginationCurrentPage,grid.options.totalItems,grid.options.paginationPageSize);
                                },
                                /**
                                 * @ngdoc method
                                 * @name seek
                                 * @methodOf ui.grid.pagination.api:PublicAPI
                                 * @description Moves to the requested page
                                 * @param {int} page The number of the page that should be displayed
                                 */
                                seek: function (page) {
                                    if (!grid.options.enablePagination) {
                                        return;
                                    }
                                    if (!angular.isNumber(page) || page < 1) {
                                        throw 'Invalid page number: ' + page;
                                    }

                                    grid.options.paginationCurrentPage = Math.min(page, publicApi.methods.pagination.getTotalPages());
                                    grid.options.pages = publicApi.methods.pagination.generatePagesArray(grid.options.paginationCurrentPage,grid.options.totalItems,grid.options.paginationPageSize);
                                },

                                calculatePageNumber : function(i, currentPage, paginationRange, totalPages) {
                                    var halfWay = Math.ceil(paginationRange/2);
                                    if (i === paginationRange) {
                                        return totalPages;
                                    } else if (i === 1) {
                                        return i;
                                    } else if (paginationRange < totalPages) {
                                        if (totalPages - halfWay < currentPage) {
                                            return totalPages - paginationRange + i;
                                        } else if (halfWay < currentPage) {
                                            return currentPage - halfWay + i;
                                        } else {
                                            return i;
                                        }
                                    } else {
                                        return i;
                                    }
                                },
                                generatePagesArray : function(currentPage, collectionLength, rowsPerPage) {
                                    var paginationRange = 7;
                                    var pages = [];
                                    var totalPages = Math.ceil(collectionLength / rowsPerPage);
                                    var halfWay = Math.ceil(paginationRange / 2);
                                    var position;

                                    if (currentPage <= halfWay) {
                                        position = 'start';
                                    } else if (totalPages - halfWay < currentPage) {
                                        position = 'end';
                                    } else {
                                        position = 'middle';
                                    }

                                    var ellipsesNeeded = paginationRange < totalPages;
                                    var i = 1;
                                    while (i <= totalPages && i <= paginationRange) {
                                        var pageNumber = publicApi.methods.pagination.calculatePageNumber(i, currentPage, paginationRange, totalPages);

                                        var openingEllipsesNeeded = (i === 2 && (position === 'middle' || position === 'end'));
                                        var closingEllipsesNeeded = (i === paginationRange - 1 && (position === 'middle' || position === 'start'));
                                        if (ellipsesNeeded && (openingEllipsesNeeded || closingEllipsesNeeded)) {
                                            pages.push('...');
                                        } else {
                                            pages.push(pageNumber);
                                        }
                                        i ++;
                                    }
                                    return pages;
                                }
                            }
                        }
                    };

                    grid.api.registerEventsFromObject(publicApi.events);
                    grid.api.registerMethodsFromObject(publicApi.methods);

                    var processPagination = function( renderableRows ){
                        if (grid.options.useExternalPagination || !grid.options.enablePagination) {
                            return renderableRows;
                        }
                        //client side pagination
                        var pageSize = parseInt(grid.options.paginationPageSize, 10);
                        var currentPage = parseInt(grid.options.paginationCurrentPage, 10);

                        var visibleRows = renderableRows.filter(function (row) { return row.visible; });
                        grid.options.totalItems = visibleRows.length;

                        var firstRow = (currentPage - 1) * pageSize;
                        if (firstRow > visibleRows.length) {
                            currentPage = grid.options.paginationCurrentPage = 1;
                            firstRow = (currentPage - 1) * pageSize;
                        }
                        return visibleRows.slice(firstRow, firstRow + pageSize);
                    };

                    grid.registerRowsProcessor(processPagination, 900 );

                },
                defaultGridOptions: function (gridOptions) {
                    /**
                     * @ngdoc object
                     * @name ui.grid.pagination.api:GridOptions
                     *
                     * @description GridOptions for the pagination feature, these are available to be
                     * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
                     */

                    /**
                     * @ngdoc property
                     * @name enablePagination
                     * @propertyOf ui.grid.pagination.api:GridOptions
                     * @description Enables pagination, defaults to true
                     */
                    gridOptions.enablePagination = gridOptions.enablePagination !== false;
                    /**
                     * @ngdoc property
                     * @name enablePaginationControls
                     * @propertyOf ui.grid.pagination.api:GridOptions
                     * @description Enables the paginator at the bottom of the grid. Turn this off, if you want to implement your
                     *              own controls outside the grid.
                     */
                    gridOptions.enablePaginationControls = gridOptions.enablePaginationControls !== false;
                    /**
                     * @ngdoc property
                     * @name useExternalPagination
                     * @propertyOf ui.grid.pagination.api:GridOptions
                     * @description Disables client side pagination. When true, handle the paginationChanged event and set data
                     *              and totalItems, defaults to `false`
                     */
                    gridOptions.useExternalPagination = gridOptions.useExternalPagination === true;
                    /**
                     * @ngdoc property
                     * @name totalItems
                     * @propertyOf ui.grid.pagination.api:GridOptions
                     * @description Total number of items, set automatically when client side pagination, needs set by user
                     *              for server side pagination
                     */
                    if (gridUtil.isNullOrUndefined(gridOptions.totalItems)) {
                        gridOptions.totalItems = 0;
                    }
                    /**
                     * @ngdoc property
                     * @name paginationPageSizes
                     * @propertyOf ui.grid.pagination.api:GridOptions
                     * @description Array of page sizes, defaults to `[250, 500, 1000]`
                     */
                    if (gridUtil.isNullOrUndefined(gridOptions.paginationPageSizes)) {
                        gridOptions.paginationPageSizes = [250, 500, 1000];
                    }
                    /**
                     * @ngdoc property
                     * @name paginationPageSize
                     * @propertyOf ui.grid.pagination.api:GridOptions
                     * @description Page size, defaults to the first item in paginationPageSizes, or 0 if paginationPageSizes is empty
                     */
                    if (gridUtil.isNullOrUndefined(gridOptions.paginationPageSize)) {
                        if (gridOptions.paginationPageSizes.length > 0) {
                            gridOptions.paginationPageSize = gridOptions.paginationPageSizes[0];
                        } else {
                            gridOptions.paginationPageSize = 0;
                        }
                    }
                    /**
                     * @ngdoc property
                     * @name paginationCurrentPage
                     * @propertyOf ui.grid.pagination.api:GridOptions
                     * @description Current page number, defaults to 1
                     */
                    if (gridUtil.isNullOrUndefined(gridOptions.paginationCurrentPage)) {
                        gridOptions.paginationCurrentPage = 1;
                    }

                    /**
                     * @ngdoc property
                     * @name paginationTemplate
                     * @propertyOf ui.grid.pagination.api:GridOptions
                     * @description A custom template for the pager, defaults to `ui-grid/pagination`
                     */
                    if (gridUtil.isNullOrUndefined(gridOptions.paginationTemplate)) {
                        gridOptions.paginationTemplate = 'ui-grid/pagination';
                    }

                    if (gridUtil.isNullOrUndefined(gridOptions.pages)) {
                        gridOptions.pages = [];
                    }
                },
                /**
                 * @ngdoc method
                 * @methodOf ui.grid.pagination.service:uiGridPaginationService
                 * @name uiGridPaginationService
                 * @description  Raises paginationChanged and calls refresh for client side pagination
                 * @param {Grid} grid the grid for which the pagination changed
                 * @param {int} currentPage requested page number
                 * @param {int} pageSize requested page size
                 */
                onPaginationChanged: function (grid, currentPage, pageSize) {
                    grid.options.pages = grid.api.pagination.generatePagesArray(currentPage,grid.options.totalItems,pageSize);
                    grid.api.pagination.raise.paginationChanged(currentPage, pageSize);
                    if (!grid.options.useExternalPagination) {
                        grid.queueGridRefresh(); //client side pagination
                    }
                }
            };

            return service;
        }
    ]);
    /**
     *  @ngdoc directive
     *  @name ui.grid.pagination.directive:uiGridPagination
     *  @element div
     *  @restrict A
     *
     *  @description Adds pagination features to grid
     *  @example
     <example module="app">
     <file name="app.js">
     var app = angular.module('app', ['ui.grid', 'ui.grid.pagination']);

     app.controller('MainCtrl', ['$scope', function ($scope) {
      $scope.data = [
        { name: 'Alex', car: 'Toyota' },
        { name: 'Sam', car: 'Lexus' },
        { name: 'Joe', car: 'Dodge' },
        { name: 'Bob', car: 'Buick' },
        { name: 'Cindy', car: 'Ford' },
        { name: 'Brian', car: 'Audi' },
        { name: 'Malcom', car: 'Mercedes Benz' },
        { name: 'Dave', car: 'Ford' },
        { name: 'Stacey', car: 'Audi' },
        { name: 'Amy', car: 'Acura' },
        { name: 'Scott', car: 'Toyota' },
        { name: 'Ryan', car: 'BMW' },
      ];

      $scope.gridOptions = {
        data: 'data',
        paginationPageSizes: [5, 10, 25],
        paginationPageSize: 5,
        columnDefs: [
          {name: 'name'},
          {name: 'car'}
        ];
       }
    }]);
     </file>
     <file name="index.html">
     <div ng-controller="MainCtrl">
     <div ui-grid="gridOptions" ui-grid-pagination></div>
     </div>
     </file>
     </example>
     */
    module.directive('uiGridPagination', ['gridUtil', 'uiGridPaginationService',
        function (gridUtil, uiGridPaginationService) {
            return {
                priority: -200,
                scope: false,
                require: 'uiGrid',
                link: {
                    pre: function ($scope, $elm, $attr, uiGridCtrl) {
                        uiGridPaginationService.initializeGrid(uiGridCtrl.grid);

                        gridUtil.getTemplate(uiGridCtrl.grid.options.paginationTemplate)
                            .then(function (contents) {
                                var template = angular.element(contents);
                                $elm.append(template);
                                uiGridCtrl.innerCompile(template);
                            });
                    }
                }
            };
        }
    ]);

    /**
     *  @ngdoc directive
     *  @name ui.grid.pagination.directive:uiGridPager
     *  @element div
     *
     *  @description Panel for handling pagination
     */
    module.directive('uiGridPager', ['uiGridPaginationService', 'uiGridConstants', 'gridUtil', 'i18nService',
        function (uiGridPaginationService, uiGridConstants, gridUtil, i18nService) {
            return {
                priority: -200,
                scope: true,
                require: '^uiGrid',
                link: function ($scope, $elm, $attr, uiGridCtrl) {
                    $scope.paginationApi = uiGridCtrl.grid.api.pagination;
                    $scope.sizesLabel = i18nService.getSafeText('pagination.sizes');
                    $scope.totalItemsLabel = i18nService.getSafeText('pagination.totalItems');

                    var options = uiGridCtrl.grid.options;

                    uiGridCtrl.grid.renderContainers.body.registerViewportAdjuster(function (adjustment) {
                        adjustment.height = adjustment.height - gridUtil.elementHeight($elm);
                        return adjustment;
                    });

                    var dataChangeDereg = uiGridCtrl.grid.registerDataChangeCallback(function (grid) {
                        if (!grid.options.useExternalPagination) {
                            grid.options.totalItems = grid.rows.length;
                        }
                    }, [uiGridConstants.dataChange.ROW]);

                    $scope.$on('$destroy', dataChangeDereg);

                    var setShowing = function () {
                        $scope.showingLow = ((options.paginationCurrentPage - 1) * options.paginationPageSize) + 1;
                        $scope.showingHigh = Math.min(options.paginationCurrentPage * options.paginationPageSize, options.totalItems);
                    };

                    var deregT = $scope.$watch('grid.options.totalItems + grid.options.paginationPageSize', setShowing);

                    var deregP = $scope.$watch('grid.options.paginationCurrentPage + grid.options.paginationPageSize', function (newValues, oldValues) {
                            if (newValues === oldValues) {
                                return;
                            }

                            if (!angular.isNumber(options.paginationCurrentPage) || options.paginationCurrentPage < 1) {
                                options.paginationCurrentPage = 1;
                                return;
                            }

                            if (options.totalItems > 0 && options.paginationCurrentPage > $scope.paginationApi.getTotalPages()) {
                                options.paginationCurrentPage = $scope.paginationApi.getTotalPages();
                                return;
                            }

                            setShowing();
                            uiGridPaginationService.onPaginationChanged($scope.grid, options.paginationCurrentPage, options.paginationPageSize);
                        }
                    );

                    $scope.$on('$destroy', function() {
                        deregT();
                        deregP();
                    });

                    $scope.cantPageForward = function () {
                        if (options.totalItems > 0) {
                            return options.paginationCurrentPage >= $scope.paginationApi.getTotalPages();
                        } else {
                            return options.data.length < 1;
                        }
                    };

                    $scope.cantPageToLast = function () {
                        if (options.totalItems > 0) {
                            return $scope.cantPageForward();
                        } else {
                            return true;
                        }
                    };

                    $scope.cantPageBackward = function () {
                        return options.paginationCurrentPage <= 1;
                    };
                }
            };
        }
    ]);
})();